// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright 2008 Sandia Corporation
// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-Sandia-USGov
#include "vtkPostgreSQLQuery.h"

#include "vtkObjectFactory.h"
#include "vtkPostgreSQLDatabase.h"
#include "vtkPostgreSQLDatabasePrivate.h"
#include "vtkStringArray.h"
#include "vtkVariant.h"
#include "vtkVariantArray.h"

#include <cassert>
#include <limits> // man, I hope all platforms have this nowadays

#include <sstream>

#define BEGIN_TRANSACTION "BEGIN"
#define COMMIT_TRANSACTION "COMMIT"
#define ROLLBACK_TRANSACTION "ROLLBACK"

#define DECLARE_CONVERTER(TargetType) vtkVariant ConvertStringTo##TargetType(bool, const char*);

VTK_ABI_NAMESPACE_BEGIN
DECLARE_CONVERTER(Boolean);
DECLARE_CONVERTER(SignedChar);
DECLARE_CONVERTER(UnsignedChar);
DECLARE_CONVERTER(SignedShort);
DECLARE_CONVERTER(UnsignedShort);
DECLARE_CONVERTER(SignedInt);
DECLARE_CONVERTER(UnsignedInt);
DECLARE_CONVERTER(SignedLong);
DECLARE_CONVERTER(UnsignedLong);
DECLARE_CONVERTER(Float);
DECLARE_CONVERTER(Double);
DECLARE_CONVERTER(VtkIdType);
DECLARE_CONVERTER(String);
DECLARE_CONVERTER(SignedLongLong);
DECLARE_CONVERTER(UnsignedLongLong);

template <typename T>
void ConvertFromNetworkOrder(T& target, const char* rawBytes)
{
  for (unsigned int i = 0; i < sizeof(T); ++i)
  {
    int targetByte = sizeof(T) - (i + 1);
    target |= (rawBytes[i] << (8 * targetByte));
  }
}

//------------------------------------------------------------------------------

vtkStandardNewMacro(vtkPostgreSQLQuery);

//------------------------------------------------------------------------------

class vtkPostgreSQLQueryPrivate
{
public:
  vtkPostgreSQLQueryPrivate()
  {
    this->QueryResults = nullptr;
    this->CurrentRow = -1;
  }
  ~vtkPostgreSQLQueryPrivate()
  {
    if (this->QueryResults)
    {
      PQclear(this->QueryResults);
    }
  }

  PGresult* QueryResults;
  int CurrentRow;
};

//------------------------------------------------------------------------------

vtkVariant vtkPostgreSQLQuery::DataValue(vtkIdType column)
{
  if (!this->IsActive())
  {
    vtkWarningMacro("DataValue() called on inactive query");
    return vtkVariant();
  }
  else if (column < 0 || column >= this->GetNumberOfFields())
  {
    vtkWarningMacro("DataValue() called with out-of-range column index " << column);
    return vtkVariant();
  }
  else if (this->QueryInternals->CurrentRow < 0)
  {
    vtkWarningMacro(
      "DataValue() cannot be called before advancing to the first row with NextRow().");
    return vtkVariant();
  }

  // Since null is independent of data type, check that next
  if (PQgetisnull(this->QueryInternals->QueryResults, this->QueryInternals->CurrentRow, column))
  {
    return vtkVariant();
  }

  int colType = this->GetFieldType(column);
  bool isBinary = this->IsColumnBinary(column);
  const char* rawData = this->GetColumnRawData(column);
  switch (colType)
  {
    case VTK_VOID:
      return vtkVariant();
    case VTK_BIT:
    {
      return ConvertStringToBoolean(isBinary, rawData);
    }
    case VTK_CHAR:
    case VTK_SIGNED_CHAR:
    {
      return ConvertStringToSignedChar(isBinary, rawData);
    }
    case VTK_UNSIGNED_CHAR:
    {
      return ConvertStringToUnsignedChar(isBinary, rawData);
    }
    case VTK_SHORT:
    {
      return ConvertStringToSignedShort(isBinary, rawData);
    }
    case VTK_UNSIGNED_SHORT:
    {
      return ConvertStringToUnsignedShort(isBinary, rawData);
    }
    case VTK_INT:
    {
      return ConvertStringToSignedInt(isBinary, rawData);
    }
    case VTK_UNSIGNED_INT:
    {
      return ConvertStringToUnsignedInt(isBinary, rawData);
    }
    case VTK_LONG:
    {
      return ConvertStringToSignedLong(isBinary, rawData);
    }
    case VTK_UNSIGNED_LONG:
    {
      return ConvertStringToUnsignedLong(isBinary, rawData);
    }
    case VTK_LONG_LONG:
    {
      return ConvertStringToSignedLongLong(isBinary, rawData);
    }
    case VTK_UNSIGNED_LONG_LONG:
    {
      return ConvertStringToUnsignedLongLong(isBinary, rawData);
    }
    case VTK_FLOAT:
    {
      return ConvertStringToFloat(isBinary, rawData);
    }
    case VTK_DOUBLE:
    {
      return ConvertStringToDouble(isBinary, rawData);
    }
    case VTK_ID_TYPE:
    {
      return ConvertStringToVtkIdType(isBinary, rawData);
    }
    case VTK_STRING:
    {
      return vtkVariant(rawData);
    }
    default:
    {
      return vtkVariant();
    }
  } // end of switch on column type
} // end of DataValue(int column)

//------------------------------------------------------------------------------
vtkPostgreSQLQuery::vtkPostgreSQLQuery()
{
  this->TransactionInProgress = false;
  this->LastErrorText = nullptr;
  this->QueryInternals = nullptr;
}

//------------------------------------------------------------------------------
vtkPostgreSQLQuery::~vtkPostgreSQLQuery()
{
  this->SetDatabase(nullptr);
  this->SetLastErrorText(nullptr);
  delete this->QueryInternals;
}

//------------------------------------------------------------------------------
void vtkPostgreSQLQuery::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Transaction in progress: " << (this->TransactionInProgress ? "YES" : "NO")
     << "\n";
  os << indent << "Last error message: " << (this->LastErrorText ? this->LastErrorText : "(null)")
     << "\n";
  os << indent << "Internals: ";
  if (this->QueryInternals)
  {
    os << this->QueryInternals;
  }
  else
  {
    os << "(null)";
  }
  os << "\n";
}

//------------------------------------------------------------------------------
bool vtkPostgreSQLQuery::Execute()
{
  if (this->Query == nullptr)
  {
    vtkErrorMacro("Cannot execute before a query has been set.");
    return false;
  }

  vtkPostgreSQLDatabase* db = vtkPostgreSQLDatabase::SafeDownCast(this->Database);
  assert(db);

  // If a query is already in progress clear out its results so we can
  // begin anew.
  if (this->QueryInternals)
  {
    this->DeleteQueryResults();
  }

  if (!db->IsOpen())
  {
    this->SetLastErrorText("Cannot execute query.  Database connection is closed.");
    vtkErrorMacro(<< "Cannot execute query.  Database connection is closed.");
    this->Active = false;
    return false;
  }

  this->QueryInternals = new vtkPostgreSQLQueryPrivate;
  this->QueryInternals->QueryResults = PQexec(db->Connection->Connection, this->Query);

  bool returnStatus;
  switch (PQresultStatus(this->QueryInternals->QueryResults))
  {
    case PGRES_EMPTY_QUERY:
    {
      returnStatus = true;
      this->Active = false;
      this->DeleteQueryResults();
      vtkWarningMacro(<< "Query string was set but empty.");
      this->SetLastErrorText(nullptr);
    };
    break;

    case PGRES_COMMAND_OK: // success on a command returning no data
    {
      returnStatus = true;
      this->Active = true;
      this->DeleteQueryResults();
      this->SetLastErrorText(nullptr);
    };
    break;

    case PGRES_TUPLES_OK:
    {
      returnStatus = true;
      this->Active = true;
      this->SetLastErrorText(nullptr);
    };
    break;

    case PGRES_BAD_RESPONSE:
    {
      returnStatus = false;
      this->Active = false;
      this->DeleteQueryResults();
      this->SetLastErrorText("Incomprehensible server response");
    };
    break;

    case PGRES_FATAL_ERROR:
    {
      returnStatus = false;
      this->Active = false;
      this->SetLastErrorText(PQerrorMessage(db->Connection->Connection));
      vtkErrorMacro(<< "Fatal error during query: " << this->GetLastErrorText());
      this->DeleteQueryResults();
    };
    break;

    default:
    {
      returnStatus = false;
      this->Active = false;
      std::ostringstream sbuf;
      sbuf << "Unhandled server response: ";
      sbuf << PQresStatus(PQresultStatus(this->QueryInternals->QueryResults));
      this->SetLastErrorText(sbuf.str().c_str());
      vtkErrorMacro(<< "Unhandled server response: " << this->GetLastErrorText());
      this->DeleteQueryResults();
    };
    break;
  }

  return returnStatus;
}

//------------------------------------------------------------------------------
int vtkPostgreSQLQuery::GetNumberOfFields()
{
  if (!this->Active || !this->QueryInternals)
  {
    vtkErrorMacro("Query is not active!");
    return 0;
  }

  return PQnfields(this->QueryInternals->QueryResults);
}

//------------------------------------------------------------------------------
const char* vtkPostgreSQLQuery::GetFieldName(int column)
{
  if (!this->Active || !this->QueryInternals->QueryResults)
  {
    vtkErrorMacro("Query is not active!");
    return nullptr;
  }
  else if (column < 0 || column >= this->GetNumberOfFields())
  {
    vtkErrorMacro("Illegal field index " << column);
    return nullptr;
  }
  return PQfname(this->QueryInternals->QueryResults, column);
}

//------------------------------------------------------------------------------
int vtkPostgreSQLQuery::GetFieldType(int column)
{
  if (!this->Active || !this->QueryInternals)
  {
    vtkErrorMacro("Query is not active!");
    return -1;
  }
  else if (column < 0 || column >= this->GetNumberOfFields())
  {
    vtkErrorMacro("Illegal field index " << column);
    return -1;
  }

  vtkPostgreSQLDatabase* db = vtkPostgreSQLDatabase::SafeDownCast(this->Database);
  if (!db)
  {
    vtkErrorMacro(<< "No database!  How did this happen?");
    return -1;
  }
  return db->Connection->GetVTKTypeFromOID(PQftype(this->QueryInternals->QueryResults, column));
}

//------------------------------------------------------------------------------
bool vtkPostgreSQLQuery::NextRow()
{
  if (!this->IsActive() || !this->QueryInternals)
  {
    vtkErrorMacro("Query is not active!");
    return false;
  }

  if (this->QueryInternals->CurrentRow < (this->GetNumberOfRows() - 1))
  {
    ++this->QueryInternals->CurrentRow;
    return true;
  }
  else
  {
    return false;
  }
}

//------------------------------------------------------------------------------
const char* vtkPostgreSQLQuery::GetLastErrorText()
{
  if (!this->Database)
  {
    return "No database";
  }
  return this->LastErrorText;
}

//------------------------------------------------------------------------------

vtkStdString vtkPostgreSQLQuery::EscapeString(vtkStdString s, bool addSurroundingQuotes)
{
  vtkStdString retval;
  if (addSurroundingQuotes)
  {
    retval = "'";
  }

  vtkPostgreSQLDatabase* db = static_cast<vtkPostgreSQLDatabase*>(this->Database);

  if (db && db->Connection)
  {
    char* escaped = new char[2 * s.size() + 1];
    int error;
    PQescapeStringConn(db->Connection->Connection, escaped, s.c_str(), s.size(), &error);
    retval.append(escaped);
    delete[] escaped;
    if (error)
    {
      vtkErrorMacro(<< "Error while escaping string.  Expect the result to be unusable.");
    }
  }
  else
  {
    retval.append(this->Superclass::EscapeString(s, false));
  }

  if (addSurroundingQuotes)
  {
    retval += "'";
  }

  return retval;
}

//------------------------------------------------------------------------------
bool vtkPostgreSQLQuery::HasError()
{
  if (!this->Database)
  {
    return false;
  }
  return this->LastErrorText != nullptr;
}

//------------------------------------------------------------------------------
bool vtkPostgreSQLQuery::BeginTransaction()
{
  if (this->TransactionInProgress)
  {
    vtkErrorMacro(<< "Cannot start a transaction.  One is already in progress.");
    return false;
  }

  vtkPostgreSQLDatabase* db = vtkPostgreSQLDatabase::SafeDownCast(this->Database);
  assert(db);
  bool status;
  PGresult* result = PQexec(db->Connection->Connection, BEGIN_TRANSACTION);
  switch (PQresultStatus(result))
  {
    case PGRES_COMMAND_OK:
    {
      this->SetLastErrorText(nullptr);
      this->TransactionInProgress = true;
      status = true;
    };
    break;
    case PGRES_FATAL_ERROR:
    {
      this->SetLastErrorText(PQresultErrorMessage(result));
      vtkErrorMacro(<< "Error in BeginTransaction: " << this->GetLastErrorText());
      status = false;
    };
    break;
    default:
    {
      this->SetLastErrorText(PQresultErrorMessage(result));
      vtkWarningMacro(<< "Unexpected return code " << PQresultStatus(result) << " ("
                      << PQresStatus(PQresultStatus(result)) << ") with error message "
                      << (this->LastErrorText ? this->LastErrorText : "(null)"));
      status = false;
    };
    break;
  }
  PQclear(result);
  return status;
}

//------------------------------------------------------------------------------
bool vtkPostgreSQLQuery::CommitTransaction()
{
  if (!this->TransactionInProgress)
  {
    vtkErrorMacro(<< "Cannot commit: no transaction is in progress.");
    return false;
  }

  vtkPostgreSQLDatabase* db = vtkPostgreSQLDatabase::SafeDownCast(this->Database);
  assert(db);

  PGresult* result = PQexec(db->Connection->Connection, COMMIT_TRANSACTION);
  bool status;
  switch (PQresultStatus(result))
  {
    case PGRES_COMMAND_OK:
    {
      this->SetLastErrorText(nullptr);
      this->TransactionInProgress = false;
      status = true;
    };
    break;
    case PGRES_FATAL_ERROR:
    {
      this->SetLastErrorText(PQresultErrorMessage(result));
      vtkErrorMacro(<< "Error in CommitTransaction: " << this->GetLastErrorText());
      this->TransactionInProgress = false;
      status = false;
    };
    break;
    default:
    {
      this->SetLastErrorText(PQresultErrorMessage(result));
      vtkWarningMacro(<< "Unexpected return code " << PQresultStatus(result) << " ("
                      << PQresStatus(PQresultStatus(result)) << ") with error message "
                      << (this->LastErrorText ? this->LastErrorText : "(null)"));
      this->TransactionInProgress = false;
      status = false;
    };
    break;
  }
  PQclear(result);
  return status;
}

//------------------------------------------------------------------------------
bool vtkPostgreSQLQuery::RollbackTransaction()
{
  if (!this->TransactionInProgress)
  {
    vtkErrorMacro(<< "Cannot rollback: no transaction is in progress.");
    return false;
  }

  vtkPostgreSQLDatabase* db = vtkPostgreSQLDatabase::SafeDownCast(this->Database);
  assert(db);

  PGresult* result = PQexec(db->Connection->Connection, ROLLBACK_TRANSACTION);
  bool status;
  switch (PQresultStatus(result))
  {
    case PGRES_COMMAND_OK:
    {
      this->SetLastErrorText(nullptr);
      this->TransactionInProgress = false;
      status = true;
    };
    break;
    case PGRES_FATAL_ERROR:
    {
      this->SetLastErrorText(PQresultErrorMessage(result));
      vtkErrorMacro(<< "Error in RollbackTransaction: " << this->GetLastErrorText());
      this->TransactionInProgress = false;
      status = false;
    };
    break;
    default:
    {
      this->SetLastErrorText(PQresultErrorMessage(result));
      vtkWarningMacro(<< "Unexpected return code " << PQresultStatus(result) << " ("
                      << PQresStatus(PQresultStatus(result)) << ") with error message "
                      << (this->LastErrorText ? this->LastErrorText : "(null)"));
      this->TransactionInProgress = false;
      status = false;
    };
    break;
  }
  PQclear(result);
  return status;
}

//------------------------------------------------------------------------------

void vtkPostgreSQLQuery::DeleteQueryResults()
{
  this->Active = false;
  delete this->QueryInternals;
  this->QueryInternals = nullptr;
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToBoolean(bool, const char* rawData)
{
  // Since there are only a few possibilities I'm going to check
  // them all by hand.
  switch (rawData[0])
  {
    case 'T':
    case 't':
    case 'Y':
    case 'y':
    case '1':
    case 1:
    {
      return vtkVariant(true);
    }

    case 'F':
    case 'f':
    case 'N':
    case 'n':
    case '0':
    case 0:
    {
      return vtkVariant(false);
    }

    default:
    {
      vtkGenericWarningMacro(<< "Unable to convert raw data to boolean.  Data length is "
                             << strlen(rawData) << " and string is '" << rawData << "'");
      return vtkVariant();
    }
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToSignedChar(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    return vtkVariant(rawData[0]);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToChar());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToUnsignedChar(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    return vtkVariant(static_cast<unsigned char>(rawData[0]));
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToUnsignedChar());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToSignedShort(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    short result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToShort());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToUnsignedShort(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    unsigned short result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToUnsignedShort());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToSignedInt(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    int result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToInt());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToUnsignedInt(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    unsigned int result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToUnsignedInt());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToSignedLong(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    signed long result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToLong());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToUnsignedLong(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    unsigned long result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToLong());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToSignedLongLong(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    long long result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToLongLong());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToUnsignedLongLong(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    unsigned long long result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    vtkVariant converter(rawData);
    return vtkVariant(converter.ToUnsignedLongLong());
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToFloat(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    // As of PostgreSQL version 8.3.0, libpq transmits a float in network
    // byte order -- that is, it reinterprets the bits as an unsigned int
    // and then transmits them that way.  This... frightens me.  It assumes
    // that both sender and recipient use IEEE floats.  Still, I'm not sure
    // there's any other good way to do it.
    unsigned int intResult = 0;
    ConvertFromNetworkOrder(intResult, rawData);

    // This is the idiom that libpq uses internally to convert between the
    // two types.
    union {
      unsigned int i;
      float f;
    } swap;
    swap.i = intResult;
    float floatResult = swap.f;

    return vtkVariant(floatResult);
  }
  else
  {
    std::string rawString(rawData);
    float finalResult;

    // Catch NaN
    if (rawData[0] == 'N' || rawData[0] == 'n')
    {
      if (std::numeric_limits<float>::has_quiet_NaN)
      {
        finalResult = std::numeric_limits<float>::quiet_NaN();
      }
      else
      {
        // C99 defines a NAN macro.  If it's there, that solves our problem.
#if defined(NAN)
        finalResult = NAN;
#else
        float zero = 0.0;
        finalResult = zero / zero;
#endif
      }
    }
    else if (rawString == "Infinity")
    {
      if (std::numeric_limits<float>::has_infinity)
      {
        finalResult = std::numeric_limits<float>::infinity();
      }
      else
      {
        finalResult = VTK_FLOAT_MAX;
      }
    }
    else if (rawString == "-Infinity")
    {
      if (std::numeric_limits<float>::has_infinity)
      {
        finalResult = -std::numeric_limits<float>::infinity();
      }
      else
      {
        finalResult = -VTK_FLOAT_MAX;
      }
    }
    else
    {
      // hurray, it's an ordinary float
      vtkVariant converter(rawData);
      finalResult = converter.ToFloat();
    }
    return vtkVariant(finalResult);
  } // end of handling string representation
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToVtkIdType(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    vtkIdType result = 0;
    ConvertFromNetworkOrder(result, rawData);
    return vtkVariant(result);
  }
  else
  {
    std::stringstream convertStream;
    convertStream.str(rawData);
    vtkIdType result;
    convertStream >> result;
    return vtkVariant(result);
  }
}

//------------------------------------------------------------------------------

vtkVariant ConvertStringToDouble(bool isBinary, const char* rawData)
{
  if (isBinary)
  {
    // As of PostgreSQL version 8.3.0, libpq transmits a float in network
    // byte order -- that is, it reinterprets the bits as an unsigned int
    // and then transmits them that way.  This... frightens me.  It assumes
    // that both sender and recipient use IEEE floats.  Still, I'm not sure
    // there's any other good way to do it.

    // Let's hope that we always have a 64-bit type.
    vtkTypeUInt64 intResult = 0;
    ConvertFromNetworkOrder(intResult, rawData);
    union {
      vtkTypeUInt64 i;
      double d;
    } swap;
    swap.i = intResult;
    return vtkVariant(swap.d);
  }
  else
  {
    double finalResult;
    std::string rawString(rawData);

    // Catch NaN
    if (rawData[0] == 'N' || rawData[0] == 'n')
    {
      if (std::numeric_limits<double>::has_quiet_NaN)
      {
        finalResult = std::numeric_limits<double>::quiet_NaN();
      }
      else
      {
        // C99 defines a NAN macro.  If it's there, that solves our problem.
#if defined(NAN)
        finalResult = NAN;
#else
        double zero = 0.0;
        finalResult = zero / zero;
#endif
      }
    }
    else if (rawString == "Infinity")
    {
      if (std::numeric_limits<double>::has_infinity)
      {
        finalResult = std::numeric_limits<double>::infinity();
      }
      else
      {
        finalResult = VTK_DOUBLE_MAX;
      }
    }
    else if (rawString == "-Infinity")
    {
      if (std::numeric_limits<double>::has_infinity)
      {
        finalResult = -std::numeric_limits<double>::infinity();
      }
      else
      {
        finalResult = -VTK_DOUBLE_MAX;
      }
    }
    else
    {
      // hurray, it's an ordinary double
      vtkVariant converter(rawData);
      finalResult = converter.ToDouble();
    }
    return vtkVariant(finalResult);
  } // end of handling string representation
}

//------------------------------------------------------------------------------

bool vtkPostgreSQLQuery::IsColumnBinary(int whichColumn)
{
  if ((!this->Active) || (!this->Database) || (!this->QueryInternals->QueryResults))
  {
    vtkWarningMacro(<< "No active query!");
    return false;
  }
  else if (whichColumn < 0 || whichColumn >= this->GetNumberOfFields())
  {
    vtkWarningMacro(<< "Illegal column index " << whichColumn);
    return false;
  }
  else
  {
    return (PQfformat(this->QueryInternals->QueryResults, whichColumn) == 1);
  }
}

//------------------------------------------------------------------------------

const char* vtkPostgreSQLQuery::GetColumnRawData(int whichColumn)
{
  if ((!this->Active) || (!this->Database) || (!this->QueryInternals->QueryResults))
  {
    vtkWarningMacro(<< "No active query!");
    return nullptr;
  }
  else if (whichColumn < 0 || whichColumn >= this->GetNumberOfFields())
  {
    vtkWarningMacro(<< "Illegal column index " << whichColumn);
    return nullptr;
  }
  else
  {
    return PQgetvalue(
      this->QueryInternals->QueryResults, this->QueryInternals->CurrentRow, whichColumn);
  }
}

//------------------------------------------------------------------------------

int vtkPostgreSQLQuery::GetNumberOfRows()
{
  if (!this->Database || !this->Database->IsOpen() || !this->QueryInternals || !this->Active)
  {
    vtkWarningMacro(<< "No active query.  Cannot retrieve number of rows.");
    return 0;
  }
  else
  {
    return PQntuples(this->QueryInternals->QueryResults);
  }
}
VTK_ABI_NAMESPACE_END
